package com.thelongrhode.core.person;

import com.thelongrhode.core.CollectionUtils;

import java.io.Serializable;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
import java.util.Set;
import java.util.SortedMap;
import java.util.TreeMap;

import org.apache.commons.lang.StringUtils;

/**
 * A personal name is defined primarily by a given name and a surname. While
 * there are numerous cultural considerations, this definition fits the majority
 * of the possible cases.
 * 
 * @author David Greenberg (greenberg.d@gmail.com)
 * @version $Id$
 */
public class Name implements Serializable {

	// Constants //

	/**
	 * The version of this class for the purpose of serialization.
	 */
	private static final long serialVersionUID = 1L;

	// Member Data //

	/**
	 * A {@link Person} may have different names in different contexts.  For
	 * instance, an actor may have a stage name in addition to a legal name.
	 * Both names are for the same {@link Person} and the same {@link Locale}.
	 * While there are no restrictions in this class for the values of this
	 * field, each system should be consistent with its use.  Two names in
	 * the same system should not have contexts of "STAGE" and "Stage Name"
	 * respectively.  Finally, this field may be {@code null} unless another
	 * name exists for the same {@code Person} and {@code Locale} as this name.
	 */
	private String context;

	/**
	 * The person that this name belongs to. Every name should be assigned to a
	 * person. While the vast majority of people will have only one name, it is
	 * possible for a person to have names in multiple languages, or a person
	 * may have changed their name. Systems should account for this possibility
	 * in their designs.
	 * 
	 * @see Person
	 */
	private Person person;

	/**
	 * The {@link Locale} for which this name has been created. If not
	 * specified, it should be assumed that the name is valid for all languages
	 * in the vast majority of circumstances. Only in cases where a specific
	 * language is absolutely required should a name be excluded on the basis of
	 * a missing language.
	 * 
	 * @see Locale
	 */
	private Locale locale;

	/**
	 * Determines whether this name is the primary name for the {@link Person}
	 * that it is linked to. This should not be confused with whether or not the
	 * name is active. A single {@link Person} can have multiple valid, active
	 * names, but only one can be the primary name.
	 */
	private boolean primary;

	/**
	 * Determines whether this name is considered an active name. When a
	 * {@link Person}'s name changes, the previous name generally becomes
	 * inactive. There is no constraint on how many names can be active for a
	 * single {@link Person}.
	 */
	private boolean active;

	private NameComponent fullName;

	// Constructors //

	/**
	 * The default constructor creates an empty name (does nothing).
	 */
	public Name() {
		// This constructor is intentionally left blank.
	}

	/**
	 * Creates a blank name for the given {@link Person}.
	 * 
	 * @param person
	 *            The {@link Person} to link this name to.
	 * 
	 * @see #setPerson(Person)
	 */
	public Name(Person person) {
		setPerson(person);
	}

	/**
	 * Creates a blank name with the given {@link Locale}.
	 * 
	 * @param locale
	 *            The {@link Locale} for this {@link Name}.
	 * 
	 * @see #setLocale(Locale)
	 */
	public Name(Locale locale) {
		setLocale(locale);
	}

	public Name(NameComponent fullName) {
		setFullName(fullName);
	}

	/**
	 * Creates a name for the given {@link Person} with the given {@link Locale}
	 * using the given {@link SortedMap} of {@link NameComponent}s. This is the
	 * preferred method to create a name.
	 * 
	 * @param person
	 *            The {@link Person} to link this name to.
	 * @param locale
	 *            The {@link Locale} of this name.
	 * 
	 * @see #setPerson(Person)
	 * @see #setLocale(Locale)
	 */
	public Name(Person person, Locale locale, NameComponent fullName) {
		setPerson(person);
		setLocale(locale);
		setFullName(fullName);
	}

	public Name(String context, Person person, Locale locale, boolean primary,
			boolean active, NameComponent fullName) {
		setContext(context);
		setPerson(person);
		setLocale(locale);
		setPrimary(primary);
		setActive(active);
		setFullName(fullName);
	}

	// Public Methods //

	public String getContext() {
		return context;
	}

	public void setContext(String context) {
		this.context = context;
	}

	/**
	 * Gives the {@link Person} that is linked to this name.
	 * 
	 * @return The {@link Person} that is associated with this name.
	 */
	public Person getPerson() {
		return person;
	}

	/**
	 * Sets the {@link Person} that is linked to this name to the given value.
	 * While a {@code null} value is allowed, it should only be used in limited
	 * circumstances.
	 * 
	 * @param person
	 *            The {@link Person} that is to be associated with this name.
	 */
	public void setPerson(Person person) {
		this.person = person;
	}

	/**
	 * Gives the {@link Locale} of this name. A single {@link Person} should
	 * only have separate names in different languages if the names are
	 * distinct.
	 * 
	 * @return The {@link Locale} of this name.
	 */
	public Locale getLocale() {
		return locale;
	}

	/**
	 * Sets the {@link Locale} of this name to the given value. While a
	 * {@code null} value is allowed, it should only be used in limited
	 * circumstances, and no implication is made for the locale represented by a
	 * {@code null} value.
	 * 
	 * @param locale
	 *            The {@link Locale} to set for this name.
	 */
	public void setLocale(Locale locale) {
		this.locale = locale;
	}

	/**
	 * Tells whether this is the primary name for the {@link Person} associated
	 * with this name. If the person is {@code null}, the value returned by this
	 * method is meaningless.
	 * 
	 * @return Whether this is the primary name for the {@link Person}
	 *         associated with it.
	 */
	public boolean isPrimary() {
		return primary;
	}

	/**
	 * Sets whether this is the primary name for the {@link Person} associated
	 * with it based on the given value. If the given value is {@code true},
	 * then this will be the primary name for that person. Otherwise, it will
	 * not.
	 * 
	 * @param primary
	 *            The value that determines whether this should be the primary
	 *            name for the {@link Person} associated with it.
	 */
	public void setPrimary(boolean primary) {
		this.primary = primary;
	}

	public boolean isActive() {
		return active;
	}

	public void setActive(boolean active) {
		this.active = active;
	}

	public NameComponent getFullName() {
		return fullName;
	}

	public void setFullName(NameComponent fullName) {
		this.fullName = fullName;
	}

	// Overridden Methods //

	public String toString() {
		NameComponent fullName = getFullName();
		if (fullName == null)
			return null;
		return fullName.toString();
	}

	// Public Static Classes //

	public static class NameComponent implements Serializable {

		// Constants //

		/**
		 * The version of this class for the purpose of serialization.
		 */
		private static final long serialVersionUID = 1L;

		// Member Data //

		private String label;

		private List<NameComponent> subComponents;

		private String value;

		// Constructors //

		public NameComponent() {
			// This constructor is intentionally left blank.
		}

		// Accessor Methods //

		public String getLabel() {
			return label;
		}

		public void setLabel(String label) {
			this.label = label;
		}

		public List<NameComponent> getSubComponents() {
			return subComponents;
		}

		public void setSubComponents(List<NameComponent> subComponents) {
			this.subComponents = subComponents;
		}

		public String getValue() {
			return value;
		}

		public void setValue(String value) {
			this.value = value;
		}

		// Overridden Methods //

		public String toString() {
			String value = getValue();
			if (value == null)
				return CollectionUtils.join(getSubComponents());
			return value;
		}

	}

}

